/**
 * Copyright (C) 2007 The AsyncMail Group <asyncmail@googlegroups.com>
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *         http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.googlecode.asyncmail.smtpserver.handler;

import com.googlecode.asyncmail.common.DSNStatus;
import com.googlecode.asyncmail.common.MailAddress;
import com.googlecode.asyncmail.common.MailEnvelope;
import com.googlecode.asyncmail.smtp.SMTPResponse;
import com.googlecode.asyncmail.smtp.SMTPResponseImpl;
import com.googlecode.asyncmail.smtp.SMTPRetCode;
import com.googlecode.asyncmail.smtpserver.SMTPHandlerConfigurationData;
import com.googlecode.asyncmail.smtpserver.SMTPSession;
import com.googlecode.asyncmail.smtpserver.core.CommandHandler;
import com.googlecode.asyncmail.smtpserver.hook.HookResult;
import com.googlecode.asyncmail.smtpserver.hook.HookResultHook;
import com.googlecode.asyncmail.smtpserver.hook.HookReturnCode;
import com.googlecode.asyncmail.smtpserver.hook.RcptHook;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;

/**
 * Handles RCPT command
 */
public class RcptCmdHandler extends AbstractHookableCmdHandler implements
        CommandHandler {

    private static final String CURRENT_RECIPIENT = "CURRENT_RECIPIENT"; // Current

    // recipient

    private HookResultHook cleanupHook;

    /**
     * Handler method called upon receipt of a RCPT command. Reads recipient.
     * Does some connection validation.
     * 
     * 
     * @param session
     *                SMTP session object
     * @param argument
     *                the argument passed in with the command by the SMTP client
     */
    protected SMTPResponse doCoreCmd(SMTPSession session, String command,
            String parameters) {
        MailAddress recipientAddress = (MailAddress) session.getState().get(
                CURRENT_RECIPIENT);
        StringBuffer response = new StringBuffer();
        response
                .append(
                        DSNStatus.getStatus(DSNStatus.SUCCESS,
                                DSNStatus.ADDRESS_VALID))
                .append(" Recipient <").append(recipientAddress).append("> OK");
        doUpdateState(session);
        return new SMTPResponseImpl(SMTPRetCode.MAIL_OK, response);

    }

    /**
     * @param session
     *                SMTP session object
     * @param argument
     *                the argument passed in with the command by the SMTP client
     */
    protected SMTPResponse doFilterChecks(SMTPSession session, String command,
            String argument) {
        String recipient = null;
        SMTPHandlerConfigurationData conf = session.getConfigurationData();

        if ((argument != null) && (argument.indexOf(":") > 0)) {
            int colonIndex = argument.indexOf(":");
            recipient = argument.substring(colonIndex + 1);
            argument = argument.substring(0, colonIndex);
        }
        if (!session.getState().containsKey(SMTPSession.MAIL_ENVELOPE)) {
            return new SMTPResponseImpl(SMTPRetCode.BAD_SEQUENCE, DSNStatus
                    .getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER)
                    + " Need MAIL before RCPT");
        } else if (argument == null
                || !argument.toUpperCase(Locale.US).equals("TO")
                || recipient == null) {
            return new SMTPResponseImpl(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
                    DSNStatus.getStatus(DSNStatus.PERMANENT,
                            DSNStatus.DELIVERY_SYNTAX)
                            + " Usage: RCPT TO:<recipient>");
        }

        recipient = recipient.trim();
        int lastChar = recipient.lastIndexOf('>');
        // Check to see if any options are present and, if so, whether they
        // are correctly formatted
        // (separated from the closing angle bracket by a ' ').
        String rcptOptionString = null;
        if ((lastChar > 0) && (recipient.length() > lastChar + 2)
                && (recipient.charAt(lastChar + 1) == ' ')) {
            rcptOptionString = recipient.substring(lastChar + 2);

            // Remove the options from the recipient
            recipient = recipient.substring(0, lastChar + 1);
        }
        if (conf.useAddressBracketsEnforcement()
                && (!recipient.startsWith("<") || !recipient.endsWith(">"))) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer errorBuffer = new StringBuffer(192).append(
                        "Error parsing recipient address: ").append(
                        "Address did not start and end with < >").append(
                        getContext(session, null, recipient));
                getLogger().error(errorBuffer.toString());
            }
            return new SMTPResponseImpl(SMTPRetCode.SYNTAX_ERROR_ARGUMENTS,
                    DSNStatus.getStatus(DSNStatus.PERMANENT,
                            DSNStatus.DELIVERY_SYNTAX)
                            + " Syntax error in parameters or arguments");
        }
        MailAddress recipientAddress = null;
        // Remove < and >
        if (conf.useAddressBracketsEnforcement()
                || (recipient.startsWith("<") && recipient.endsWith(">"))) {
            recipient = recipient.substring(1, recipient.length() - 1);
        }

        if (recipient.indexOf("@") < 0) {
            // set the default domain
            recipient = recipient + "@"
                    + session.getConfigurationData().getDefaultDomain();
        }

        try {
            recipientAddress = new MailAddress(recipient);
        } catch (Exception pe) {
            if (getLogger().isErrorEnabled()) {
                StringBuffer errorBuffer = new StringBuffer(192).append(
                        "Error parsing recipient address: ").append(
                        getContext(session, recipientAddress, recipient))
                        .append(pe.getMessage());
                getLogger().error(errorBuffer.toString());
            }
            /*
             * from RFC2822; 553 Requested action not taken: mailbox name not
             * allowed (e.g., mailbox syntax incorrect)
             */
            return new SMTPResponseImpl(SMTPRetCode.SYNTAX_ERROR_MAILBOX, DSNStatus
                    .getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYNTAX)
                    + " Syntax error in recipient address");
        }

        if (rcptOptionString != null) {

            StringTokenizer optionTokenizer = new StringTokenizer(
                    rcptOptionString, " ");
            while (optionTokenizer.hasMoreElements()) {
                String rcptOption = optionTokenizer.nextToken();
                int equalIndex = rcptOption.indexOf('=');
                String rcptOptionName = rcptOption;
                String rcptOptionValue = "";
                if (equalIndex > 0) {
                    rcptOptionName = rcptOption.substring(0, equalIndex)
                            .toUpperCase(Locale.US);
                    rcptOptionValue = rcptOption.substring(equalIndex + 1);
                }
                // Unexpected option attached to the RCPT command
                if (getLogger().isDebugEnabled()) {
                    StringBuffer debugBuffer = new StringBuffer(128).append(
                            "RCPT command had unrecognized/unexpected option ")
                            .append(rcptOptionName).append(" with value ")
                            .append(rcptOptionValue).append(
                                    getContext(session, recipientAddress,
                                            recipient));
                    getLogger().debug(debugBuffer.toString());
                }

                return new SMTPResponseImpl(SMTPRetCode.PARAMETER_NOT_IMPLEMENTED,
                        "Unrecognized or unsupported option: " + rcptOptionName);
            }
            optionTokenizer = null;
        }

        session.getState().put(CURRENT_RECIPIENT, recipientAddress);

        return null;
    }

    private String getContext(SMTPSession session,
            MailAddress recipientAddress, String recipient) {
        StringBuffer sb = new StringBuffer(128);
        if (null != recipientAddress) {
            sb.append(" [to:" + recipientAddress.toString() + "]");
        } else if (null != recipient) {
            sb.append(" [to:" + recipient + "]");
        }
        if (null != session.getState().get(SMTPSession.MAIL_ENVELOPE)) {
            MailAddress sender = ((MailEnvelope) session.getState().get(
                    SMTPSession.MAIL_ENVELOPE)).getSender();
            if (sender != null) {
                sb.append(" [from:" + sender.getAddressAsString() + "]");
            }
        }
        return sb.toString();
    }

    /**
     * @see com.googlecode.asyncmail.smtpserver.core.CommandHandler#getImplCommands()
     */
    public Collection<String> getImplCommands() {
        Collection<String> implCommands = new ArrayList<String>();
        implCommands.add("RCPT");
        return implCommands;
    }

    /**
     * @see com.googlecode.asyncmail.smtpserver.handler.AbstractHookableCmdHandler#getHookInterface()
     */
    protected Class<?> getHookInterface() {
        return RcptHook.class;
    }

    /**
     * @see com.googlecode.asyncmail.smtpserver.handler.AbstractHookableCmdHandler#callHook(java.lang.Object,
     *      com.googlecode.asyncmail.smtpserver.SMTPSession, java.lang.String)
     */
    protected HookResult callHook(Object rawHook, SMTPSession session,
            String parameters) {
        return ((RcptHook) rawHook).doRcpt(session, ((MailEnvelope) session
                .getState().get(SMTPSession.MAIL_ENVELOPE)).getSender(),
                (MailAddress) session.getState().get(CURRENT_RECIPIENT));
    }

    @Override
    protected HookResultHook getCleanupHook() {
        if (cleanupHook == null) {
            cleanupHook = new HookResultHook() {

                /**
                 * @see com.googlecode.asyncmail.smtpserver.hook.HookResultHook#onHookResult(com.googlecode.asyncmail.smtpserver.SMTPSession,
                 *      com.googlecode.asyncmail.smtpserver.hook.HookResult,
                 *      java.lang.Object)
                 */
                public HookResult onHookResult(SMTPSession session,
                        HookResult result, Object object) {
                    if (result.getResult() == HookReturnCode.DENY
                            || result.getResult() == HookReturnCode.DENYSOFT) {
                        session.getState().remove(CURRENT_RECIPIENT);
                    } else if (result.getResult() == HookReturnCode.DECLINED) {
                        // if the hook declined creates a new MailEnvelope and
                        // add the
                        // recicpient
                        doUpdateState(session);
                    }
                    return result;
                }

            };
        }
        return cleanupHook;
    }

    /**
     * @param session
     */
    private void doUpdateState(SMTPSession session) {
        MailEnvelope newEnv = (MailEnvelope) session.getState().get(
                SMTPSession.MAIL_ENVELOPE);

        List<MailAddress> rcptColl = newEnv.getRecipients();
        if (rcptColl == null) {
            rcptColl = new LinkedList<MailAddress>();
            newEnv.setRecipients(rcptColl);
        }
        MailAddress recipientAddress = (MailAddress) session.getState().get(
                CURRENT_RECIPIENT);
        rcptColl.add(recipientAddress);

        session.getState().put(SMTPSession.MAIL_ENVELOPE, newEnv);
    }

}
